﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using NUnit.Framework;
using Smith.AMC;
using Smith.AMC.Configuration;
using Smith.AMC.Network;

namespace MemcachedClientTests
{
    [TestFixture]
    public class AddTest
    {
        private List<KeyValuePair<IPAddress, int>> servers = new List<KeyValuePair<IPAddress, int>>();
        private ServerNodeMonitorConfiguration config = new ServerNodeMonitorConfiguration();

        [TestFixtureSetUp]
        public void PerTestSetUp()
        {
            servers.Add(new KeyValuePair<IPAddress, int>(IPAddress.Parse("127.0.0.1"), 11211));
        }



        [Test]
        public void AddShouldReturnTrue()
        {
            ServerPool pool = new ServerPool(servers, SocketType.Stream, ProtocolType.Tcp, 25, config);
            MemcachedClient client = new MemcachedClient(new SocketWorker(pool, TimeSpan.FromSeconds(20)));

            string cacheKey = Guid.NewGuid().ToString();
            Console.WriteLine(cacheKey);

            ICacheItem<long> cacheItem = new CacheItem<long>
            {
                CacheKey = cacheKey,
                LifeTime = TimeSpan.FromSeconds(0),
                Value = 1234567890
            };

            ManualResetEvent evt = new ManualResetEvent(false);
            bool cacheResponse = false;
            client.BeginAdd(cacheItem, ar =>
            {
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                try
                {
                    cacheResponse = client.EndAdd<long>(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                evtInner.Set();
            }, evt);
            evt.WaitOne();
            Assert.IsTrue(cacheResponse);
        }

        [Test]
        public void AddShouldMakeLongAvailableForGet()
        {
            ServerPool pool = new ServerPool(servers, SocketType.Stream, ProtocolType.Tcp, 25, config);
            MemcachedClient client = new MemcachedClient(new SocketWorker(pool, TimeSpan.FromSeconds(20)));

            ICacheItem<long> cacheItem = new CacheItem<long>
            {
                CacheKey = Guid.NewGuid().ToString(),
                LifeTime = TimeSpan.FromSeconds(0),
                Value = 1234567890
            };

            ManualResetEvent evt = new ManualResetEvent(false);

            client.BeginAdd(cacheItem, ar =>
            {
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                try
                {
                    client.EndAdd<long>(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                evtInner.Set();
            }, evt);
            evt.WaitOne();
            ManualResetEvent evt2 = new ManualResetEvent(false);
            ICacheItem<long> itemRetrieved = null;
            client.BeginGet<long>(cacheItem.CacheKey, ar =>
            {
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                try
                {
                    itemRetrieved = client.EndGet<long>(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                evtInner.Set();
            }, evt2);
            evt2.WaitOne();

            Assert.IsNotNull(itemRetrieved);
            Assert.AreEqual(cacheItem.Value, itemRetrieved.Value);


        }

        [Test]
        public void AddShouldOnlyAddItemIfItsNoAlreadyThere()
        {
            ServerPool pool = new ServerPool(servers, SocketType.Stream, ProtocolType.Tcp, 25, config);
            MemcachedClient client = new MemcachedClient(new SocketWorker(pool, TimeSpan.FromSeconds(20)));

            ICacheItem<long> cacheItem = new CacheItem<long>
            {
                CacheKey = Guid.NewGuid().ToString(),
                LifeTime = TimeSpan.FromSeconds(0),
                Value = 1234567890
            };

            ManualResetEvent evt = new ManualResetEvent(false);

            client.BeginAdd(cacheItem, ar =>
            {
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                try
                {
                    client.EndAdd<long>(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                evtInner.Set();
            }, evt);
            evt.WaitOne();


            // Add again, with different value
            ManualResetEvent evt2 = new ManualResetEvent(false);

            ICacheItem<long> cacheItem2 = new CacheItem<long>
            {
                CacheKey = cacheItem.CacheKey,
                LifeTime = TimeSpan.FromSeconds(0),
                Value = 0123456
            };

            bool secondAddResponse = false;
            client.BeginAdd(cacheItem2, ar =>
            {
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                try
                {
                    secondAddResponse = client.EndAdd<long>(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                evtInner.Set();
            }, evt2);
            evt2.WaitOne();

            


            ManualResetEvent evt3 = new ManualResetEvent(false);
            ICacheItem<long> itemRetrieved = null;
            client.BeginGet<long>(cacheItem.CacheKey, ar =>
            {
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                try
                {
                    itemRetrieved = client.EndGet<long>(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                evtInner.Set();
            }, evt3);
            evt3.WaitOne();

            Assert.IsFalse(secondAddResponse);

            Assert.IsNotNull(itemRetrieved);
            // Assert that the value retrived is equal to the first time we added, and not the second time
            Assert.AreEqual(cacheItem.Value, itemRetrieved.Value);
        }

        [Test]
        public void AddShouldMakeObjectAvailableForGet()
        {
            ServerPool pool = new ServerPool(servers, SocketType.Stream, ProtocolType.Tcp, 25, config);
            MemcachedClient client = new MemcachedClient(new SocketWorker(pool, TimeSpan.FromSeconds(20)));
            ObjectToSerialize obj = new ObjectToSerialize
            {
                Age = 36,
                Name = "Bjørn Bouet Smith"
            };

            ICacheItem<ObjectToSerialize> cacheItem = new CacheItem<ObjectToSerialize>
            {
                CacheKey = Guid.NewGuid().ToString(),
                LifeTime = TimeSpan.FromSeconds(0),
                Value = obj
            };

            ManualResetEvent evt = new ManualResetEvent(false);

            client.BeginAdd(cacheItem, ar =>
            {
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                try
                {
                    client.EndAdd<ObjectToSerialize>(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                evtInner.Set();
            }, evt);
            evt.WaitOne();
            ManualResetEvent evt2 = new ManualResetEvent(false);
            ICacheItem<ObjectToSerialize> itemRetrieved = null;
            client.BeginGet<ObjectToSerialize>(cacheItem.CacheKey, ar =>
            {
                ManualResetEvent evtInner = (ManualResetEvent)ar.AsyncState;
                try
                {
                    itemRetrieved = client.EndGet<ObjectToSerialize>(ar);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
                evtInner.Set();
            }, evt2);
            evt2.WaitOne();

            Assert.IsNotNull(itemRetrieved);
            ObjectToSerialize objectRetrieved = itemRetrieved.Value;
            Assert.AreEqual(obj.Age, objectRetrieved.Age);
            

        }

        [Serializable]
        private class ObjectToSerialize
        {

            public string Name
            {
                get;
                set;
            }

            public long Age
            {
                get;
                set;
            }
        }

    }
}
